#!/bin/bash
# Initializations
END="$(tput sgr0)"
UNDERLINE="$(tput smul)"
YELLOW="$(tput setaf 3)"
RED="$(tput setaf 1)"
BLUE="$(tput setaf 4)" ORANGE=$(tput setaf 172)
MAGENTA="$(tput setaf 5)"
CYAN="$(tput setaf 6)"
WHITE="$(tput setaf 7)"
ARGC=$#
SPEC="./plugin.spec.yaml"
ORDER_FILE="tests/order.txt"
DEBUG_TRUE=0
VOL_TRUE=0
CACHE_TRUE=0
PORT_TRUE=0
MAKE_TRUE=0
JSON_RUN=0
JSON_TEST_ACK=0
JSON_TEST_ALL=0
JSON_RUN_ALL=0
JSON_RUN_ACK=0
ASSESS=0
COMMANDS='info|sample|samples|test|run|http|bash|stats'
ARGV="$@"
CMD="$ARGV"
EARG=$3 # Extra arg for sample name and stats options

# functions
usage()
{
cat <<EOF
${MAGENTA}Plugin runner${END}
      Processing Options:
        ${YELLOW}-h${END}      Help (this message)
        ${YELLOW}-f${END}      Plugin spec file (def: ./plugin.spec.yaml)
        ${YELLOW}-A${END}      Create PR assessment around the given -T/-R options
        ${YELLOW}-C${END}      Enable cache (def: /var/cache)
        ${YELLOW}-c${END}      Command to run in container (e.g. $COMMANDS)
        ${YELLOW}-d${END}      Debug mode (i.e.: --debug)
        ${YELLOW}-j${END}      Pipe through JQ
        ${YELLOW}-m${END}      Build plugin before executing
        ${YELLOW}-v${END}      Volume mount (e.g.: /src:/dst)
        ${YELLOW}-p${END}      Port forward (e.g.: 8000:8888/udp)
        ${YELLOW}-T${END}      Run JSON test methods (e.g. <file.json> | all) all - Execution order can be specified via tests/order.txt
        ${YELLOW}-R${END}      Run JSON run methods (e.g. <file.json> | all) all - Execution order can be specified via tests/order.txt
${WHITE}Usage${END}: $0 [-c <cmd>] [-j] [-d] [-A] [-C] [-m] [-T <test.json|all>] [-R <test.json|all>] -v <src_vol:dst_vol> -p <src_port:dst_port>
EOF
}

die(){
    printf -- "${RED}$*${END}\n" >&2
    exit 1
}

hi(){
    printf -- "${YELLOW}$*${END}\n" >&2
}

info(){
    printf -- "${MAGENTA}$*${END}\n" >&2
}

get_vendor(){
    local vendor
    vendor=$(grep '^vendor: ' $SPEC | sed 's/vendor: //')
    printf "$vendor"
}

get_name(){
    local name
    name=$(grep '^name: ' $SPEC | sed 's/name: //')
    printf "$name"
}

get_version(){
    local version
    version=$(grep '^version: ' $SPEC | sed 's/version: //')
    printf "$version"
}

get_plugin_spec_version(){
    local version
    version=$(grep '^plugin_spec_version: ' $SPEC | sed 's/plugin_spec_version: //')
    printf "$version"
}

get_actions(){
    local actions
    actions=$(../tools//stats.py $SPEC | grep 'Action List:' | sed 's/  Action List: //')
    printf "$actions"
}

get_triggers(){
    local triggers
    triggers=$(../tools/stats.py $SPEC | grep 'Trigger List:' | sed 's/  Trigger List: //')
    printf "$triggers"
}

get_stats(){
    local stats
    local arg=$1
    [[ $arg ]] && cd .. && stats=$(./tools/stats.py $arg) && cd $OLDPWD || stats=$(../tools/stats.py $SPEC)
    printf "$stats"
}

files_by_order(){
    local files
    if [[ -f $ORDER_FILE ]]; then
        [[ -s $ORDER_FILE ]] || die "Order file is empty! tests/order.txt requires a list of JSON test filenames to guarantee the execution order when using the all argument."
        files=$(cat $ORDER_FILE)
    else
        files=tests/*
    fi
    printf "$files"
}

run_json_method(){
    local method="$1"
    [[ $ASSESS = 1 ]] && JQ="| grep -- ^\{ | jq -r '.body | try(.log | split(\"\\\\n\") | .[]),.output'" && create_assessment_header "$method"

    # JSON test file logic
    [[ $method == "test" ]] && [[ $JSON_TEST_ALL = 1 ]] && FILES=$(files_by_order)
    [[ $method == "run" ]] && [[ $JSON_RUN_ALL = 1 ]] && FILES=$(files_by_order)


    [[ $method == "test" ]] && FILE=$TEST_FILE
    [[ $method == "run" ]] && FILE=$RUN_FILE

    for json in $FILE $FILES; do
        [[ -f $json ]] || die "File $json does not exist, can't continue"
        grep -q '"trigger":' $json && DEBUG="--debug" && TRIGGER=1
        RUN="cat $json | docker run --rm "$VOLUME" "$PORT_FWD" -i "${VENDOR}/${NAME}:${VERSION}" "$DEBUG" "$method" ${JQ}"
        hi "Running: $RUN"
        [[ $TRIGGER = 1 ]] && info "This is a trigger ^C to continue" && unset TRIGGER
        [[ $ASSESS = 1 ]] && json=$(eval $RUN) || eval $RUN
        [[ $DEBUG_TRUE = 0 ]] && unset DEBUG
        [[ $ASSESS = 1 ]] && create_assessment_output "$json"
        printf "\n"
    done
    unset FILE
    unset FILES
}

create_assessment_header(){
    header="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}"
    printf -- "### $header\n"
    printf -- "Autogenerated with:\n\`$0 $ARGV\`\n"
}

create_assessment_validation(){
    printf -- '```\n'
    printf -- "$(make validate)\n"
    printf -- '```\n'
    printf -- '\n'
}

create_assessment_output(){
    local json="$1"
    printf -- "<details>\n\n"
    printf -- '```\n'
    printf -- '%s\n' "${json}"
    printf -- '```\n'
    printf -- "<summary>\n"
    printf -- "\$ ${RUN}\n"
    printf -- "</summary>\n"
    printf -- "</details>\n"
}

create_assessment_end(){
    printf "### UI\n\nScreenshots of the plugin being used in Komand for validating use go below this line.\n"
    printf "#### Workflow Builder\n\n"
    printf "#### Job\n\n"
    printf "#### Artifact\n\n"
}

# if less than 1 argument
if [[ $ARGC < 1 ]]; then
    usage
    exit 1
fi

while getopts "hAc:Cdf:jmT:R:f:p:v:" OPTION; do
    case $OPTION in
    v)
        VOL_TRUE=1
        if [[ -d "${OPTARG%:*}" ]]; then
            VOL="$OPTARG"
        else
            die "Directory $VOL doesn't exist"
        fi
        [[ $VOL =~ : ]] || die "Invalid argument as e.g. \`\`-v /var/cache:/var/cache\'\'" ;;
    p)
        PORT_TRUE=1
        PORTS="$OPTARG"
        [[ $PORTS =~ [0-9]+:[0-9]+ ]] || die "Invalid argument as e.g. \`\`-p 8000:8888\'\'"
        ;;
    c)
        CMD="$OPTARG"
        if [[ "$CMD" == info ]]; then
        :
        elif [[ "$CMD" == sample ]]; then
        :
        elif [[ "$CMD" == samples ]]; then
        :
        elif [[ "$CMD" == test ]]; then
        :
        elif [[ "$CMD" == run ]]; then
        :
        elif [[ "$CMD" == http ]]; then
        :
        elif [[ "$CMD" == bash ]]; then
        :
        elif [[ "$CMD" == stats ]]; then
        :
        else
        die "Unknown command type! ($COMMANDS)"
        fi
        ;;
    C)
        CACHE_TRUE=1;;
    T)
        if [[ -f "$OPTARG" ]]; then
            TEST_FILE="$OPTARG"
            JSON_TEST_ACK=1
        elif [[ "$OPTARG" == all ]]; then
            JSON_TEST_ALL=1
            JSON_TEST_ACK=1
        else
            die "Input JSON file $OPTARG doesn't exist"
        fi
        ;;
    R)
        if [[ -f "$OPTARG" ]]; then
            RUN_FILE="$OPTARG"
            JSON_RUN_ACK=1
        elif [[ "$OPTARG" == all ]]; then
            JSON_RUN_ALL=1
            JSON_RUN_ACK=1
        else
            die "Input JSON file $OPTARG doesn't exist"
        fi
        ;;
    A)
        ASSESS=1;;
    f)
        if [[ -f "$OPTARG" ]]; then
            SPEC="$OPTARG"
        else
            die "Input plugin spec file $OPTARG doesn't exist"
        fi
        ;;
    j)
        JQ="| grep -- ^\{ | jq -r '.body | try(.log | split(\"\\\\n\") | .[]),.output'";;
    m)
        MAKE_TRUE=1;;
    d)
        DEBUG_TRUE=1
        DEBUG="--debug";;
    h)
        usage
        exit 1
        ;;
    \?)
        exit 1
        ;;
    esac
done

# Volume
if [[ $VOL_TRUE = 1 ]]; then
    VOLUME="$VOL"
fi

# Cache
if [[ $CACHE_TRUE = 1 ]]; then
    VOLUME="${VOL:-/var/cache:/var/cache}"
fi

if [[ $CACHE_TRUE = 1 ]] || [[ $VOL_TRUE = 1 ]]; then
    VOLUME="-v $VOLUME"
fi

# Port
if [[ $PORT_TRUE = 1 ]]; then
    PORT_FWD="-p $PORTS"
fi

VENDOR=$(get_vendor)
NAME=$(get_name)
VERSION=$(get_version)
PLUGIN_SPEC_VERSION=$(get_plugin_spec_version)

# Stats
if [[ $CMD = "stats" ]]; then
    if [[ "$EARG" == "all" ]]; then
        EARG=total
    elif [[ "$EARG" == "python" ]]; then
        :
    elif [[ "$EARG" == "go" ]]; then
        :
    elif [[ "$EARG" == "" ]]; then
        :
    else
        die "Stats requires an argument e.g. \`\`./run.sh -c stats [all|python|go]\'\'"
    fi
    get_stats $EARG
    exit 0
fi

# Make
if [[ $MAKE_TRUE = 1 ]]; then
    hi "Building plugin first"
    [[ $SPEC =~ '..' ]] && BUILD="../$NAME" && make -C $BUILD || make
fi

if [[ $CMD == "samples" ]]; then
    [[ -d tests ]] || mkdir "tests"
    TRIGGERS=$(get_triggers)
    ACTIONS=$(get_actions)
    for sample in $TRIGGERS $ACTIONS; do
        RUN="docker run --rm -ti "${VENDOR}/${NAME}:${VERSION}" sample $sample | jq '.' > tests/${sample}.json"
        hi "Running: $RUN"
        eval $RUN
    done
    exit 0
fi

if [[ $CMD == "bash" ]]; then
    [[ $EARG ]] && CMD="$EARG"
    RUN="docker run --rm --entrypoint "$CMD" "$VOLUME" "$PORT_FWD" -ti "${VENDOR}/${NAME}:${VERSION}" "$DEBUG" ${JQ}"
    hi "Running: $RUN"
    eval $RUN
    exit 0
fi

[[ $ASSESS = 1 ]] && printf "## Assessment\n"
[[ $ASSESS = 1 ]] && create_assessment_header Validation "make validate" && create_assessment_validation
[[ $JSON_TEST_ACK == 1 ]] && run_json_method "test"
[[ $JSON_RUN_ACK == 1 ]] && run_json_method "run"
[[ $ASSESS = 1 ]] && create_assessment_end

# Exit if we ran run_json_method as everything after are plugin bin commands
[[ $JSON_RUN_ACK == 1 ]] && exit 0
[[ $JSON_TEST_ACK == 1 ]] && exit 0

# Start plugin bin commands
if [[ "$CMD" == info ]]; then
    :
elif [[ "$CMD" == sample ]]; then
    if [[ $EARG ]]; then
        CMD="$CMD $EARG | jq '.'"
    else
        hi "Actions: [$(get_actions)]"
        hi "Triggers: [$(get_triggers)]"
        die "Sample requires sample name e.g. \`\`./run.sh -c sample <name>\'\'"
    fi
elif [[ "$CMD" == test ]]; then
    :
elif [[ "$CMD" == run ]]; then
    :
elif [[ "$CMD" == http ]]; then

    if [[ $PLUGIN_SPEC_VERSION != v2 ]]; then
        die "Plugin does not support proxy mode"
    fi

    # 10001 is the default start port for the plugin proxy, so we repeat its use here
    i=10001

    # Loop until there's an open port
    until ! nc -z localhost ${i} &>/dev/null
    do
        i=$((i + 1))
    done
    printf "${YELLOW}Forwarding to port ${i}${END}\n"

    PORT_FWD="-d $PORT_FWD -p ${i}:10001"
else
    unset CMD
fi

RUN="$JSON_TEST docker run --rm "$VOLUME" "$PORT_FWD" -i "${VENDOR}/${NAME}:${VERSION}" "$DEBUG" "$CMD" ${JQ}"
hi "Running: $RUN"
eval $RUN

exit 0
